package dev.hxliu.cn.methodHandles;

import dev.hxliu.cn.inject.InjectMethod;
import dev.hxliu.cn.inject.InjectMethodTypeEnum;
import dev.hxliu.cn.inject.InjectValue;
import org.springframework.beans.BeanUtils;
import org.springframework.core.ResolvableType;
import org.springframework.util.Assert;

import java.beans.PropertyDescriptor;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.UnaryOperator;

public record InjectValueHandle(MethodHandle bound, boolean isCover, MethodHandle read,
                                MethodHandle write, InjectMethodTypeEnum enumType) {

    public static Field[] getFieldsDirectly(Class<?> beanClass, boolean withSuperClassFields) throws SecurityException {
        Assert.notNull(beanClass,"beanClass must not be null");

        Field[] allFields = null;
        Class<?> searchType = beanClass;
        Field[] declaredFields;
        while (searchType != null) {
            declaredFields = searchType.getDeclaredFields();
            if (null == allFields) {
                allFields = declaredFields;
            } else {
                Field[] result = Arrays.copyOf(allFields, allFields.length + declaredFields.length);
                System.arraycopy(declaredFields, 0, result, allFields.length, declaredFields.length);
                allFields = result;
            }
            searchType = withSuperClassFields ? searchType.getSuperclass() : null;
        }

        return allFields;
    }
    private static final MethodHandles.Lookup lookup = MethodHandles.lookup();
//    public static final Map<Class<?>, InjectValueHandle[]> INJECT_VALUE_CACHE = Collections.synchronizedMap(new WeakHashMap<>()) ;
    public static final Map<Class<?>, InjectValueHandle[]> INJECT_VALUE_CACHE = new ConcurrentHashMap<>();

    public static InjectValueHandle[] getInjectValueHandleList(Class<?> clazz) {
        return  INJECT_VALUE_CACHE.computeIfAbsent(clazz, (key) ->{

            Field[] fields = getFieldsDirectly(key, true);
            return Arrays.stream(fields).parallel().map(field->{
                InjectValue injectValue = field.getAnnotation(InjectValue.class);
                if(injectValue != null) {
                    Class<? extends InjectMethod<?>>  supplier = injectValue.value();
                    try {
                    PropertyDescriptor propertyDescriptor = BeanUtils.getPropertyDescriptor(clazz, field.getName());
                        if(propertyDescriptor!=null) {

                            // TODO (1) PropertyDescriptor 提供了获取属性的读方法和写方法的方法。

//                            Method readMethod = propertyDescriptor.getReadMethod();
//                            MethodHandle read = MethodHandles.lookup().unreflect(readMethod);
//                            Method writeMethod = propertyDescriptor.getWriteMethod();
//                            MethodHandle write = MethodHandles.lookup().unreflect(writeMethod);


                            // TODO (2) 直接拼getSet方法

//                                String capitalize = StringUtils.capitalize(field.getName());
//                                MethodHandle write = lookup.findVirtual(clazz, "set" + capitalize,
//                                        MethodType.methodType(void.class, field.getType()));
//                                MethodHandle read = lookup.findVirtual(clazz, "get" + capitalize,
//                                        MethodType.methodType(field.getType()));
                        // 私有对象反射句柄
                        field.setAccessible(true);
                        MethodHandle write = lookup.unreflectSetter(field);
                        MethodHandle read = lookup.unreflectGetter(field);
                            InjectMethod<?> injectMethod = injectValue.createType().getConvertType().apply(supplier);
                            MethodHandleInjectType methodHandleInjectType = of(supplier, injectMethod);
                            return new InjectValueHandle(methodHandleInjectType.methodHandle(), injectValue.isCover(),
                                    read, write, methodHandleInjectType.enumType());
                        }
                            } catch (Throwable e) {
                                throw new RuntimeException(e);
                            }
                }
                return null;
            }).filter(Objects::nonNull).toArray(InjectValueHandle[]::new);

        }) ;
    }
   static final   MethodType methodType = MethodType.methodType(Object.class, Object.class);

    public static MethodHandleInjectType of(Class<? extends InjectMethod<?>> supplier, InjectMethod<?> injectMethod) throws Throwable {
        MethodHandles.Lookup newLookup = MethodHandles.privateLookupIn(supplier,lookup);
        ResolvableType resolvableType = ResolvableType.forClass(supplier);
        ResolvableType injectMethodType = resolvableType.as(InjectMethod.class);
        ResolvableType genericType = injectMethodType.getGeneric();
        MethodHandleInjectType methodHandleInjectType ;
        Class<?> rawClass = genericType.getRawClass();
        if (rawClass != null) {
            MethodType mt = MethodType.methodType(rawClass);
            // InjectMethod类的inject句柄
            MethodHandle mh = newLookup.findVirtual(supplier, "inject", mt);
            if (UnaryOperator.class.isAssignableFrom(rawClass)) {
                // invoke调用生成对应的UnaryOperator对象-一般为lambda表达式

//                MethodHandle bind = mh.bindTo(injectMethod);
//                MethodHandle apply = MethodHandles.lookup().findVirtual(UnaryOperator.class, "apply",
//                MethodType.methodType(Object.class, Object.class));
//                MethodHandle combo = MethodHandles.collectArguments(apply,0,bind );
//                methodHandleInjectType = new MethodHandleInjectType(InjectMethodTypeEnum.InjectMethodArgColumn, combo);

                // inject句柄绑定injectMethod对象-直接返回injectMethod对象的inject方法句柄
                MethodHandle methodHandle = mh.bindTo(injectMethod);
                // invoke调用生成对应的UnaryOperator对象-一般为lambda表达式
                Object invoke = methodHandle.invoke();
                // bind绑定apply方法-直接返回UnaryOperator对象的apply方法句柄

//                MethodHandle apply = MethodHandles.lookup().findVirtual(UnaryOperator.class, "apply",
//                        MethodType.methodType(Object.class, Object.class));
//                MethodHandle combo = MethodHandles.collectArguments(apply,0,methodHandle );
//                methodHandleInjectType = new MethodHandleInjectType(InjectMethodTypeEnum.InjectMethodArgColumn, combo);
                methodHandleInjectType = new MethodHandleInjectType(InjectMethodTypeEnum.InjectMethodArgColumn,
                        newLookup.bind(invoke, "apply", methodType));

            }else if (Function.class.isAssignableFrom(rawClass)) {
                MethodHandle methodHandle = mh.bindTo(injectMethod);
                Object invoke = methodHandle.invoke();
                methodHandleInjectType = new MethodHandleInjectType(InjectMethodTypeEnum.InjectMethodArgObject,newLookup.bind(invoke, "apply", methodType));
            }else{
                methodHandleInjectType= new MethodHandleInjectType(InjectMethodTypeEnum.InjectMethod,mh.bindTo(injectMethod));
            }
            return methodHandleInjectType;
        }
        methodHandleInjectType= new MethodHandleInjectType(InjectMethodTypeEnum.NONE,null);
        return methodHandleInjectType;
    }
    public record MethodHandleInjectType(InjectMethodTypeEnum enumType,MethodHandle methodHandle){

    }


}
